﻿using System.Linq;
// <copyright file="ApplicationDataService.cs" company="Gravity Age Studios">
// Code released under the MIT license (see License.txt).
// </copyright>
// <author>Vicente Cartas Espinel</author>
// <email>vicente.cartas@gmail.com</email>
namespace LightSwitchApplication
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.LightSwitch;

    public partial class ApplicationDataService
    {
        #region Sorted Queries

        partial void DomainProvincesSortedByFullName_PreprocessQuery(ref IQueryable<DomainProvince> query)
        {
            query = query.OrderBy(d => d.BaseProvince.Region).ThenBy(d => d.BaseProvince.Name);
        }

        partial void GuildHoldingsSorted_PreprocessQuery(ref IQueryable<HoldingGuild> query)
        {
            query = query.OrderBy(h => h.Province.BaseProvince.Region).ThenBy(h => h.Province.BaseProvince.Name);
        }

        #endregion

        /// <summary>
        /// Query to calculate the scout report.
        /// </summary>
        /// <param name="query">The query.</param>
        partial void ArmiesScoutReport_PreprocessQuery(ref IQueryable<Army> query)
        {
            var domainsIds = this.GetActiveUserDomainsIds();
            var domains = this.GetActiveUserDomains();

            // Provinces the player can see because he has holdings on them
            var provinces = domains.SelectMany(d => d.Provinces);
            var provFromLaw = domains.SelectMany(d => d.LawHoldings).Select(h => h.Province);
            var provFromTemples = domains.SelectMany(d => d.TempleHoldings).Select(h => h.Province);
            var provFromGuilds = domains.SelectMany(d => d.GuildHoldings).Select(h => h.Province);
            var provFromSources = domains.SelectMany(d => d.SourceHoldings).Select(h => h.Province);

            // Provinces the player can see because of units capable of scouting
            var armies = domains.SelectMany(d => d.Armies);
            var scoutingByLand = armies.SelectMany(a => a.LandUnits)
                                    .Where(u => !string.IsNullOrEmpty(u.BaseLandUnit.GMInformation) && u.BaseLandUnit.GMInformation.Contains("Scout")).Select(u => u.Army.Location);
            var scoutingBySea = armies.SelectMany(a => a.SeaUnits)
                                    .Where(u => !string.IsNullOrEmpty(u.BaseSeaUnit.GMInformation) && u.BaseSeaUnit.GMInformation.Contains("Scout")).Select(u => u.Army.Location);
            var neighboursFromLand = scoutingByLand.SelectMany(p => this.Neighbours(p));
            var neighboursFromSea = scoutingBySea.SelectMany(p => this.Neighbours(p));

            var totalProvincesIds = provinces
                                .Concat(provFromLaw).Concat(provFromTemples).Concat(provFromGuilds).Concat(provFromSources)
                                .Concat(scoutingByLand).Concat(scoutingBySea).Concat(neighboursFromLand).Concat(neighboursFromSea)
                                .Distinct()
                                .Select(p => p.Id);

            query = query
                        .Where(a => a.Owner == null || !domainsIds.Contains(a.Owner.Id))
                        .Where(a => totalProvincesIds.Contains(a.Location.Id));
        }

        /// <summary>
        /// Query that calculates which provinces are selected as neighbours of a given province.
        /// </summary>
        /// <param name="query">The query.</param>
        partial void BaseProvinceNeighbours_PreprocessQuery(int? provinceId, ref IQueryable<BaseProvince> query)
        {
            if (!provinceId.HasValue || provinceId == 0)
            {
                return;
            }

            var province = this.BaseProvinces_Single(provinceId);
            var totalRelations = province.Neighbours.Concat(province.InvertedNeighbours);

            // We don't know if the neighbour province is the start or the end of the relation, so we just test all relations to find out
            List<int> neighboursIds = new List<int>();
            foreach (var relation in totalRelations)
            {
                if (relation.StartProvince.Id == provinceId)
                {
                    neighboursIds.Add(relation.EndProvince.Id);
                }
                else
                {
                    neighboursIds.Add(relation.StartProvince.Id);
                }
            }

            neighboursIds = neighboursIds.Distinct().ToList();

            query = query.Where(c => neighboursIds.Contains(c.Id));
        }

        /// <summary>
        /// Trade routes in a domain province.
        /// </summary>
        /// <param name="provinceId">The province id.</param>
        /// <param name="query">The query.</param>
        partial void TradeRoutesInDomainProvince_PreprocessQuery(int? provinceId, ref IQueryable<TradeRoute> query)
        {
            if (provinceId.HasValue)
            {
                query = query
                        .Where(tr => tr.StartPoint.Province.Id == provinceId ||
                                    (tr.EndPoint != null && tr.EndPoint.Province.Id == provinceId))
                        .Distinct();
            }
        }

        /// <summary>
        /// Ley lines in a domain province.
        /// </summary>
        /// <param name="provinceId">The province id.</param>
        /// <param name="query">The query.</param>
        partial void LeyLinesInDomainProvince_PreprocessQuery(int? provinceId, ref IQueryable<LeyLine> query)
        {
            if (provinceId.HasValue)
            {
                query = query
                        .Where(ll => ll.StartPoint.Province.Id == provinceId ||
                                    (ll.EndPoint != null && ll.EndPoint.Province.Id == provinceId))
                        .Distinct();
            }
        }

        #region Not Selected Queries (DELETE THIS!)

        /// <summary>
        /// Query that calculates which BloodPowers have not been already selected by a given Character.
        /// </summary>
        /// <param name="characterId">The Character id.</param>
        /// <param name="query">The query.</param>
        partial void BloodAbilitiesNotSelectedByCharacter_PreprocessQuery(int? characterId, ref IQueryable<BloodAbility> query)
        {
            if (!characterId.HasValue || characterId == 0)
            {
                return;
            }

            var usedIds = this.Characters_Single(characterId).RelationCharacterBloodAbilities.Select(r => r.BloodAbility.Id);
            query = query.Where(c => !usedIds.Contains(c.Id));
        }

        /// <summary>
        /// Query that calculates which Classes have not been already selected by a given Character.
        /// </summary>
        /// <param name="characterId">The Character id.</param>
        /// <param name="query">The query.</param>
        partial void ClassesNotSelectedByCharacter_PreprocessQuery(int? characterId, ref IQueryable<CharacterClass> query)
        {
            if (!characterId.HasValue || characterId == 0)
            {
                return;
            }

            var usedIds = this.Characters_Single(characterId).RelationCharacterClasses.Select(r => r.CharacterClass.Id);
            query = query.Where(c => !usedIds.Contains(c.Id));
        }

        /// <summary>
        /// Query that calculates which Skills have not been already selected by a given Character.
        /// </summary>
        /// <param name="characterId">The Character id.</param>
        /// <param name="query">The query.</param>
        partial void SkillsNotSelectedByCharacter_PreprocessQuery(int? characterId, ref IQueryable<Skill> query)
        {
            if (!characterId.HasValue || characterId == 0)
            {
                return;
            }

            var usedIds = this.Characters_Single(characterId).RelationCharacterSkills.Select(r => r.Skill.Id);
            query = query.Where(c => !usedIds.Contains(c.Id));
        }

        /// <summary>
        /// Query that calculates which Modifiers have not been already assigned to a given Trait.
        /// </summary>
        /// <param name="traitId">The Trait id.</param>
        /// <param name="query">The query.</param>
        partial void ModifiersNotSelectedByTrait_PreprocessQuery(int? traitId, ref IQueryable<Modifier> query)
        {
            if (!traitId.HasValue || traitId == 0)
            {
                return;
            }

            var usedIds = this.Traits_Single(traitId).RelationTraitModifiers.Select(r => r.Modifier.Id);
            query = query.Where(m => !usedIds.Contains(m.Id));
        }

        #endregion

        #region Player Owned Queries

        /// <summary>
        /// Gets the current Player logged in the client.
        /// </summary>
        /// <param name="query">The query.</param>
        partial void CurrentPlayer_PreprocessQuery(ref IQueryable<Player> query)
        {
            int? playerId = this.GetActiveUserPlayerId();
            if (!playerId.HasValue || playerId == 0)
            {
                throw new ApplicationException(
                    string.Format("Couldn't find the current active Player. Current user was \"{0}\"", this.Application.User.FullName));
            }

            query = query.Where(p => p.Id == playerId.Value);
        }

        /// <summary>
        /// Gets the list of Armies for a given Player.
        /// </summary>
        /// <param name="query">The query.</param>
        partial void ArmiesOwnedByPlayer_PreprocessQuery(ref IQueryable<Army> query)
        {
            var ids = this.GetActiveUserDomainsIds();

            query = query
                .Where(a => ids.Contains(a.Owner.Id));
        }

        /// <summary>
        /// Gets the list of BaseBuildings designed by a given Player.
        /// </summary>
        /// <param name="query">The query.</param>
        partial void BaseBuildingsOwnedByPlayer_PreprocessQuery(ref IQueryable<BaseBuilding> query)
        {
            var ids = this.GetActiveUserDomainsIds();

            query = query
                .Where(bb => ids.Contains(bb.Designer.Id));
        }

        /// <summary>
        /// Gets the list of BaseLandsUnits designed by a given Player.
        /// </summary>
        /// <param name="query">The query.</param>
        partial void BaseLandUnitsOwnedByPlayer_PreprocessQuery(ref IQueryable<BaseLandUnit> query)
        {
            var ids = this.GetActiveUserDomainsIds();

            query = query
                .Where(lu => ids.Contains(lu.Designer.Id));
        }

        /// <summary>
        /// Gets the list of BaseSeaUnits designed by a given Player.
        /// </summary>
        /// <param name="query">The query.</param>
        partial void BaseSeaUnitsOwnedByPlayer_PreprocessQuery(ref IQueryable<BaseSeaUnit> query)
        {
            var ids = this.GetActiveUserDomainsIds();

            query = query
                .Where(su => ids.Contains(su.Designer.Id));
        }

        /// <summary>
        /// Gets the list of Characters for a given Player.
        /// </summary>
        /// <param name="query">The query.</param>
        partial void CharactersOwnedByPlayer_PreprocessQuery(ref IQueryable<Character> query)
        {
            var ids = this.GetActiveUserCharactersIds();

            query = query
                .Where(c => ids.Contains(c.Id));
        }

        /// <summary>
        /// Gets the list of Domains for a given Player.
        /// </summary>
        /// <param name="query">The query.</param>
        partial void DomainsOwnedByPlayer_PreprocessQuery(ref IQueryable<Domain> query)
        {
            var ids = this.GetActiveUserDomainsIds();

            query = query
                .Where(d => ids.Contains(d.Id));
        }

        /// <summary>
        /// Gets the list of DomainTurns for a given Player.
        /// </summary>
        /// <param name="query">The query.</param>
        partial void DomainTurnsOwnedByPlayer_PreprocessQuery(ref IQueryable<DomainTurn> query)
        {
            var ids = this.GetActiveUserDomainsIds();

            query = query
                .Where(dt => ids.Contains(dt.Domain.Id));
        }

        /// <summary>
        /// Gets the list of RealmSpells designed by a given Player.
        /// </summary>
        /// <param name="query">The query.</param>
        partial void RealmSpellsOwnedByPlayer_PreprocessQuery(ref IQueryable<RealmSpell> query)
        {
            var ids = this.GetActiveUserCharactersIds();

            query = query
                .Where(rs => ids.Contains(rs.Researcher.Id));
        }

        /// <summary>
        /// Gets the list of Notifications for a given Player.
        /// </summary>
        /// <param name="query">The query.</param>
        partial void NotificationsForPlayer_PreprocessQuery(ref IQueryable<Notification> query)
        {
            int? playerId = this.GetActiveUserPlayerId();
            if (!playerId.HasValue || playerId == 0)
            {
                throw new ApplicationException(
                    string.Format("Couldn't find Player to fetch Notifications. Current user was \"{0}\"", this.Application.User.FullName));
            }

            query = query.Where(n => n.Recipient.Id == playerId);
        }

        partial void LeyLinesOwnedByDomain_PreprocessQuery(int? domainId, ref IQueryable<LeyLine> query)
        {
            List<int> holdingsIds = new List<int>();
            if (domainId != null)
            {
                var domain = this.Domains_Single(domainId);
                holdingsIds = domain.SourceHoldings.Select(p => p.Id).ToList();
            }

            query = query
                        .Where(ll => holdingsIds.Contains(ll.StartPoint.Id) ||
                                    (ll.EndPoint != null && holdingsIds.Contains(ll.EndPoint.Id)))
                        .Distinct();
        }

        partial void TradeRoutesOwnedByDomain_PreprocessQuery(int? domainId, ref IQueryable<TradeRoute> query)
        {
            List<int> holdingsIds = new List<int>();
            if (domainId != null)
            {
                var domain = this.Domains_Single(domainId);
                holdingsIds = domain.GuildHoldings.Select(p => p.Id).ToList();
            }

            query = query
                        .Where(tr => holdingsIds.Contains(tr.StartPoint.Id) ||
                                    (tr.EndPoint != null && holdingsIds.Contains(tr.EndPoint.Id)))
                        .Distinct();
        }

        #endregion

        #region Helper Methods

        private int? GetActiveUserPlayerId()
        {
            foreach (Player p in this.Players)
            {
                if (p.UserName.Equals(this.Application.User.Name, StringComparison.InvariantCultureIgnoreCase))
                {
                    return p.Id;
                }
            }

            return null;
        }

        private List<int> GetActiveUserCharactersIds()
        {
            int? playerId = this.GetActiveUserPlayerId();

            if (!playerId.HasValue)
            {
                return new List<int>();
            }
            else
            {
                return this.Players_Single(playerId).Characters
                    .Where(c => c.Details.EntityState != EntityState.Added)
                    .Select(c => c.Id)
                    .ToList();
            }
        }

        private List<int> GetActiveUserDomainsIds()
        {
            int? playerId = this.GetActiveUserPlayerId();

            if (!playerId.HasValue)
            {
                return new List<int>();
            }
            else
            {
                return this.Players_Single(playerId).Characters
                    .SelectMany(c => c.Domains)
                    .Select(d => d.Id)
                    .ToList();
            }
        }

        private List<Domain> GetActiveUserDomains()
        {
            int? playerId = this.GetActiveUserPlayerId();

            if (!playerId.HasValue)
            {
                return new List<Domain>();
            }
            else
            {
                return this.Players_Single(playerId).Characters
                    .SelectMany(c => c.Domains)
                    .ToList();
            }
        }

        private List<DomainProvince> Neighbours(DomainProvince province)
        {
            var ends = province.BaseProvince.Neighbours.Select(r => r.EndProvince.DomainProvince);
            var starts = province.BaseProvince.InvertedNeighbours.Select(r => r.StartProvince.DomainProvince);

            return ends.Concat(starts).Distinct().ToList();
        }

        #endregion
    }
}
